use super::{HttpRequest, HttpResponse};
use regex::Regex;
use std::{
    collections::HashMap,
    io::{prelude::*, BufReader, BufWriter, Error, ErrorKind},
    net::TcpStream,
};

const URL_REGEX: &str = concat!(
    r"(?i)",                                      // case insensitive
    r"^(?:([A-Z]+)://){0,1}",                     // 1: schema
    r"(?:([A-Z0-9-_~!.%]+):([A-Z0-9-_~!.%]+)@)?", // 2: username 3: password
    r"((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|",
    r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?)|",
    r"(?:\[(?:(?:[A-F0-9]{0,4}:){3,7}[A-F0-9]{0,4})\])|",
    r"localhost|",
    r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", // 4: host
    r"(:\d+)?",                             // 5: port (include : )
    r"(/?|[/?]\S+)$"                        // 6 path
);

fn find_socket_addr_and_path(url: &str) -> Option<(String, String)> {
    let re = Regex::new(URL_REGEX).unwrap();
    if let Some(cap) = re.captures(url) {
        log::debug!("captures url {:?} to {:?}", url, cap);
        let host = cap.get(4).unwrap().as_str();
        let port = if let Some(port) = cap.get(5) {
            port.as_str()
        } else {
            match cap.get(1) {
                Some(schema) => match schema.as_str().to_lowercase().as_str() {
                    "https" => ":443",
                    _ => ":80",
                },
                None => ":80",
            }
        };
        let path = match cap.get(6) {
            Some(path_match) => path_match.as_str(),
            None => "/",
        };
        Some((host.to_string() + port, path.to_string()))
    } else {
        None
    }
}

pub fn decoder_request(stream: TcpStream) -> Result<HttpRequest, Box<dyn std::error::Error>> {
    let mut headers = HashMap::new();
    let mut proxy_headers = HashMap::new();
    let mut reader = BufReader::new(stream.try_clone()?);

    let mut line = String::new();
    reader.read_line(&mut line)?;
    log::debug!("first line read: {:?}", line);

    let parts: Vec<&str> = line.split(' ').collect();
    if parts.len() < 3 {
        return Err(Box::new(Error::new(
            ErrorKind::InvalidData,
            "This is not a valid HTTP Request. ",
        )));
    }
    let method = parts[0].to_uppercase();
    let url = parts[1];
    let ver = parts[2].trim();

    loop {
        let mut line = String::new();
        reader.read_line(&mut line)?;
        log::debug!("line read:: {:?}", line);
        if line.trim() == "" {
            break;
        }
        if let Some(sp) = line.find(":") {
            let key = &line[..sp];
            let val = line[sp + 1..].trim();
            if key.starts_with("Proxy-") {
                proxy_headers.insert(key.to_string(), val.to_string());
            } else {
                headers.insert(key.to_string(), val.to_string());
            }
        } else {
            return Err(Box::new(Error::new(
                ErrorKind::InvalidData,
                "Cannot decode headers from line: ".to_string() + line.trim(),
            )));
        };
    }

    let (addr, path) = if let Some(res) = find_socket_addr_and_path(url) {
        res
    } else {
        if let Some(host) = headers.get("Host") {
            if let Some(res) = find_socket_addr_and_path(host) {
                res
            } else {
                return Err(Box::new(Error::new(
                    ErrorKind::AddrNotAvailable,
                    "".to_string(),
                )));
            }
        } else {
            return Err(Box::new(Error::new(
                ErrorKind::AddrNotAvailable,
                "".to_string(),
            )));
        }
    };

    Ok(HttpRequest {
        method: method,
        socket_addr: addr,
        path: path,
        version: ver.to_string(),
        headers: headers,
        proxy_headers: proxy_headers,
        reader: reader,
        writer: BufWriter::new(stream),
    })
}

pub fn decode_response(stream: TcpStream) -> Result<HttpResponse, Box<dyn std::error::Error>> {
    let mut headers = HashMap::new();
    let mut reader = BufReader::new(stream.try_clone()?);

    let mut line = String::new();
    reader.read_line(&mut line)?;
    // log::debug!("{:?}", line);

    loop {
        let mut line = String::new();
        reader.read_line(&mut line)?;
        if line.trim() == "" {
            break;
        }
        if let Some(sp) = line.find(":") {
            let key = &line[..sp];
            let val = line[sp + 1..].trim();
            headers.insert(key.to_string(), val.to_string());
        } else {
            return Err(Box::new(Error::new(
                ErrorKind::InvalidData,
                "Cannot decode headers from line: ".to_string() + line.trim(),
            )));
        };
    }

    Ok(HttpResponse {
        response_line: line,
        headers: headers,
        reader: reader,
    })
}
